#include "ly/cell.hpp"
#include "ly/manager.hpp"

#include <entt/entt.hpp>

namespace ly {

struct CellOp : public Op {
  CellOp() {}
  virtual ~CellOp() {}

  virtual void redo(Cell*) const = 0;
  virtual void undo(Cell*) const = 0;
};

class SwapLayerOp : public CellOp {
 public:
  SwapLayerOp(unsigned int a, unsigned int b) : m_a(a), m_b(b) {}

  virtual void redo(Cell* cell) const { cell->swap(m_a, m_b); }

  virtual void undo(Cell* cell) const { cell->swap(m_a, m_b); }

 private:
  unsigned int m_a, m_b;
};

struct SetCellPropId : public CellOp {
  SetCellPropId(properties_id_type f, properties_id_type t) : m_from(f), m_to(t) {}

  virtual void redo(Cell* cell) const { cell->prop_id(m_to); }

  virtual void undo(Cell* cell) const { cell->prop_id(m_from); }

 private:
  properties_id_type m_from, m_to;
};

Cell::shapes_sptr Cell::shapes(shape_id_type id) {
  auto it = m_shapes.find(id);
  if (it == m_shapes.end()) {
    auto shapes = Shapes::create(std::dynamic_pointer_cast<Cell>(shared_from_this()));
    m_shapes[id] = shapes;
    return shapes;
  } else {
    return it->second;
  }
}

Cell::instances_sptr Cell::instances() {
  if (!m_insts) {
    m_insts = Instances::create(this_sptr());
  }

  return m_insts;
}

void Cell::prop_id(properties_id_type id) {
  if (m_prop_id != id) {
    if (manager() && manager()->transacting()) {
      manager()->queue(this_sptr(), std::make_shared<SetCellPropId>(m_prop_id, id));
    }
    m_prop_id = id;
  }
}

void Cell::swap(unsigned int i1, unsigned int i2) {
  if (i1 != i2) {
    if (manager() && manager()->transacting()) {
      // manager()->queue(this, new SwapLayerOp(i1, i2));
    }
  }
}

void Cell::redo(Op* op) {
  CellOp* cell_op = dynamic_cast<CellOp*>(op);
  if (cell_op) {
    //  redo operation
    cell_op->redo(this);
  } else {
    //  other actions are only queued by the instance list - this is should be
    //  responsible for the handling of the latter.
    //  HACK: this is not really a nice concept, but it saves us a pointer to the manager.
    m_insts->redo(op);
  }
}

void Cell::undo(Op* op) {
  CellOp* cell_op = dynamic_cast<CellOp*>(op);
  if (cell_op) {
    //  undo operation
    cell_op->undo(this);
  } else {
    //  other actions are only queued by the instance list - this is should be
    //  responsible for the handling of the latter.
    //  HACK: this is not really a nice concept, but it saves us a pointer to the manager.
    m_insts->undo(op);
  }
}

}  // namespace ly
